package com.limit.token;

import com.limit.annotation.TokenParamAnnotation;
import com.limit.exception.BizException;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 令牌桶算法
 * hbz
 */
@Slf4j
public class TokensLimiter {

    static {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            TokensLimiter tokensLimiter = TokensLimiter.getInstance();
            // 获取令牌信息集合
            Map<String, String> tokenParam = tokensLimiter.tokenParam;
            // 遍历map集合t
            Iterator<Map.Entry<String, String>> it = tokenParam.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, String> entry = it.next();
                String[] tokensValue = entry.getValue().split("_");
                // 获取出生成速率，并刷新剩余令牌数为每秒的生成速率
                String rate = tokensValue[0];
                entry.setValue(rate + "_" + rate);
            }
        }, 0, 1000, TimeUnit.MILLISECONDS);
    }

    private volatile static TokensLimiter instance;

    private TokensLimiter() {
    }

    // 最后一次令牌发放时间
    public long timeStamp = System.currentTimeMillis();
    // 桶的容量
    public int capacity = 10;
    //    // 令牌生成速度10/s
//    public int rate = 10;
//    // 当前令牌数量
//    public int tokens = 10;
    // 令牌参数 key=类名+方法名， value = 生成令牌速度(rete)_当前剩余令牌数量(tokens)
    public Map<String, String> tokenParam = new HashMap<>();

    public static TokensLimiter getInstance() {
        if (instance == null) {
            synchronized (TokensLimiter.class) {
                if (instance == null) {
                    instance = new TokensLimiter();
                }
            }
        }
        return instance;
    }

    /**
     * 限流逻辑
     *
     * @param className  //获取访问接口的类名
     * @param method // 获取访问接口的方法名
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     */
    public void acquire(String className, Method method) throws ClassNotFoundException, NoSuchMethodException {

        Class a = Class.forName(className);
        if(method == null){
            return;
        }
        boolean annotationPresentMethod = method.isAnnotationPresent(TokenParamAnnotation.class);
        if (annotationPresentMethod) {
            // 获取接口类型中的值
            TokenParamAnnotation annotation = method.getAnnotation(TokenParamAnnotation.class);
            this.tokensBucket(className, method, annotation);
            return;
        }

        boolean annotationPresentClass = a.isAnnotationPresent(TokenParamAnnotation.class);
        if (annotationPresentClass) {
            // 获取接口类型中的值
            TokenParamAnnotation annotation = (TokenParamAnnotation) a.getAnnotation(TokenParamAnnotation.class);
            this.tokensBucket(className, method, annotation);
            return;
        }
    }

    /**
     * 令牌桶
     * @param className    类名
     * @param method   方法名
     * @param annotation   注解实例
     */
    private void tokensBucket(String className, Method method, TokenParamAnnotation annotation){
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        StringBuilder stringBuilder = new StringBuilder(className + methodName);
        for(Class c : parameterTypes){
            stringBuilder.append(c);
        }
        System.err.println("最终key: " + stringBuilder.toString().trim());
        // 判断该方法的注解，是否存在于令牌集合中
        if (tokenParam.get(stringBuilder.toString().trim()) == null) {
            // 不存在，则加入,刚加入剩余令牌数肯定要和生成速度一致
            tokenParam.put(stringBuilder.toString().trim(), annotation.thread() + "_" + annotation.thread());
        }

        // 获取该方法的令牌信息
        String tokenDetail = tokenParam.get(stringBuilder.toString().trim());
        String[] rateAndTokenCount = tokenDetail.split("_");
        // 获取当前令牌数
        int tokens = Integer.valueOf(rateAndTokenCount[1]);

        synchronized (this) {
            if (tokens < 1) {
                // 若不到令牌,则拒绝
                log.info("限流了");
                throw new BizException(-1, annotation.msg());
            } else {
                // 还有令牌，领取令牌
                tokens -= 1;
                log.info("剩余令牌=" + tokens);
                // 把剩余令牌更新到map集合中
                tokenParam.put(stringBuilder.toString().trim(), annotation.thread() + "_" + tokens);
            }
        }
    }

}
